package nl.windgazer.arachne.services.captcha;

import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.awt.image.ImageFilter;
import java.util.Locale;

import nl.windgazer.arachne.util.ArachneSymbolConstants;

import org.apache.tapestry5.ioc.services.SymbolSource;

import com.jhlabs.image.RippleFilter;
import com.octo.captcha.CaptchaFactory;
import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;
import com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator;
import com.octo.captcha.component.image.color.RandomListColorGenerator;
import com.octo.captcha.component.image.deformation.ImageDeformation;
import com.octo.captcha.component.image.deformation.ImageDeformationByFilters;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.fontgenerator.RandomFontGenerator;
import com.octo.captcha.component.image.textpaster.RandomTextPaster;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.wordtoimage.DeformedComposedWordToImage;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.engine.GenericCaptchaEngine;
import com.octo.captcha.image.gimpy.GimpyFactory;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;

/**
 * This service is recommended as a SingleTon by the JCaptcha team. Let's hope I can make that work
 * with T5 configuration options somehow (Yes, I hate hate xml....)
 * 
 * @author mreuring
 *
 */
public class ImageCaptchaServiceImp implements ImageCaptchaService {
	
    private final DefaultManageableImageCaptchaService captchaService;
    private final boolean uppercase;
    private final boolean lowercase;

    public ImageCaptchaServiceImp(SymbolSource symbolSource) {
    	//This is the generator that creates the captcha word from the configured characters
    	WordGenerator wg = new RandomWordGenerator(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_CHARS));

	    //build filters
	    com.jhlabs.image.WaterFilter water = new com.jhlabs.image.WaterFilter();

	    water.setAmplitude(3d);
	    water.setAntialias(true);
	    water.setPhase(20d);
	    water.setWavelength(70d);

	    RippleFilter ripple = new RippleFilter();
	    ripple.setWaveType(5);
	    ripple.setXAmplitude(5);
	    ripple.setYAmplitude(5);
	    ripple.setXWavelength(10);
	    ripple.setYWavelength(10);
	    ripple.setEdgeAction(1);

	    ImageDeformation backDef = new ImageDeformationByFilters(
	            new ImageFilter[]{});
	    ImageDeformation textDef = new ImageDeformationByFilters(
	            new ImageFilter[]{ripple});
	    ImageDeformation postDef = new ImageDeformationByFilters(
	            new ImageFilter[]{});

	    //Setting up the configured background color
    	final int width = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_IMG_WIDTH));
    	final int height = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_IMG_HEIGHT));
    	final Color bgColor = getColor(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_BACKGROUND));
    	BackgroundGenerator bg = new UniColorBackgroundGenerator(width,height,bgColor);

    	//Splitting and configuring the colors configured for the captcha text.
    	String[] sColors = symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_COLORS).split(",");
    	Color[] colors = new Color[sColors.length];
    	for (int i = 0; i < sColors.length; i++) {
    		colors[i] = getColor(sColors[i]);
    	}
    	RandomListColorGenerator colorGen = new RandomListColorGenerator(colors);

    	//Splitting and instantiating the Fonts configured for this app.
    	String[] sFonts = symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_FONTS).split(",");
    	Font[] fonts = new Font[sFonts.length];
    	for (int i = 0; i < sFonts.length; i++) {
    		fonts[i] = new Font(sFonts[i], 0, 10);
    	}
    	int minSize = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_FONTS_MIN));
    	int maxSize = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_FONTS_MAX));
    	FontGenerator fontGenerator = new RandomFontGenerator(minSize, maxSize, fonts);

    	//Setting up the 'word' generator
    	int min = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_CHARS_MIN));
    	int max = Integer.parseInt(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_CHARS_MAX));
    	TextPaster tp = new RandomTextPaster(min,max,colorGen);

    	//Combining everything into a captcha-generator
    	WordToImage wti = new DeformedComposedWordToImage(fontGenerator, bg, tp, backDef, textDef, postDef);
    	GimpyFactory gf = new GimpyFactory(wg, wti);
    	CaptchaFactory[] factories = new CaptchaFactory[] {
    		gf
    	};

    	GenericCaptchaEngine engine = new GenericCaptchaEngine(factories);
    	captchaService = new DefaultManageableImageCaptchaService();
    	captchaService.setCaptchaEngine(engine);

    	lowercase = Boolean.parseBoolean(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_CHARS_LOWERCASE));
    	uppercase = Boolean.parseBoolean(symbolSource.valueForSymbol(ArachneSymbolConstants.CAPTCHA_CHARS_UPPERCASE));
    }

	public BufferedImage getImageChallengeForID(String captchaId, Locale locale) {
		return captchaService.getImageChallengeForID(captchaId, locale);
	}

	public boolean validateResponseForID(String captchaId, String inputChars, Locale locale) {
		if (uppercase)
			return captchaService.validateResponseForID(captchaId, inputChars.toUpperCase(locale));
		else if (lowercase)
			return captchaService.validateResponseForID(captchaId, inputChars.toLowerCase(locale));
		return captchaService.validateResponseForID(captchaId, inputChars);
	}

	private Color getColor(String hex) {
		int r = Integer.parseInt(hex.substring(0,2),16);
		int g = Integer.parseInt(hex.substring(2,4),16);
		int b = Integer.parseInt(hex.substring(4,6),16);
		int a = hex.length() > 6?Integer.parseInt(hex.substring(6),16):255;
		return new Color(r,g,b,a);
	}

}
